package com.jfirer.validator.validation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.ElementKind;
import javax.validation.MessageInterpolator;
import javax.validation.ParameterNameProvider;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.GroupConversionDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import com.jfirer.validator.BeanValidator;
import com.jfirer.validator.ConstraintViolationGenerator;
import com.jfirer.validator.ExecutableDescriptorFactory;
import com.jfirer.validator.PropertyValueDescriptorFactory;
import com.jfirer.validator.RedefineDefaultGroupDescriptor;
import com.jfirer.validator.WritableConstraintViolation;
import com.jfirer.validator.context.ConstraintValidatorContextImpl;
import com.jfirer.validator.descriptor.BeanDescriptorImpl;
import com.jfirer.validator.descriptor.ConstraintDescriptorImpl;
import com.jfirer.validator.path.api.ConnectionNode;
import com.jfirer.validator.path.api.ConnectionNodeImpl;
import com.jfirer.validator.util.Commons;
import com.jfirer.baseutil.StringUtil;

public class ValidatorImpl implements Validator, BeanValidator
{
	private final MessageInterpolator            messageInterpolator;
	private final ConstraintValidatorFactory     constraintValidatorFactory;
	private final PropertyValueDescriptorFactory propertyValueDescriptorFactory;
	private final ExecutableValidator            executableValidator;
	private       Map<Class<?>, BeanDescriptor>  store	= new ConcurrentHashMap<Class<?>, BeanDescriptor>();
	
	public ValidatorImpl(ParameterNameProvider parameterNameProvider, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, PropertyValueDescriptorFactory propertyValueDescriptorFactory)
	{
		this.messageInterpolator = messageInterpolator;
		this.constraintValidatorFactory = constraintValidatorFactory;
		this.propertyValueDescriptorFactory = propertyValueDescriptorFactory;
		executableValidator = new ExecutableValidatorImpl(parameterNameProvider, this, constraintValidatorFactory);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups)
	{
		if (object == null)
		{
			return new HashSet<ConstraintViolation<T>>();
		}
		Set<Class<?>>               validateGroups = Commons.defineValidateGroups(groups);
		Set<ConstraintViolation<T>> result         = new HashSet<ConstraintViolation<T>>();
		ConnectionNode              node           = new ConnectionNodeImpl().setElementKind(ElementKind.BEAN);
		validateBean(result, validateGroups, node, object);
		for (ConstraintViolation<T> constraintViolation : result)
		{
			((WritableConstraintViolation<T>) constraintViolation).setRootBean(object);
			((WritableConstraintViolation<T>) constraintViolation).setRootBeanClass((Class<T>) object.getClass());
		}
		return result;
	}
	
	@Override
	public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups)
	{
		if (object == null)
		{
			return new HashSet<ConstraintViolation<T>>();
		}
		Set<Class<?>> validateGroups = Commons.defineValidateGroups(groups);
		BeanDescriptor beanDescriptor = getConstraintsForClass(object.getClass());
		PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(propertyName);
		if (propertyDescriptor == null)
		{
			throw new ValidationException(StringUtil.format("类:{}不存在属性:{}", object.getClass().getName(), propertyName));
		}
		Set<ConstraintViolation<T>> result = new HashSet<ConstraintViolation<T>>();
		ConnectionNode propertyNode = new ConnectionNodeImpl().setElementKind(ElementKind.BEAN).next().setElementKind(ElementKind.PROPERTY).setName(propertyName);
		Object leafBean = object;
		Object validateValue = propertyValueDescriptorFactory.getInstance(object.getClass(), propertyDescriptor).propertyValue(object);
		if (propertyDescriptor.hasConstraints())
		{
			validateConstraintedElement(result, validateGroups, propertyDescriptor.getConstraintDescriptors(), propertyNode, leafBean, validateValue);
		}
		if (propertyDescriptor.isCascaded())
		{
			validateCascadedElement(result, validateGroups, propertyNode, validateValue);
		}
		return result;
	}
	
	@Override
	public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups)
	{
		BeanDescriptor beanDescriptor = getConstraintsForClass(beanType);
		if (value == null)
		{
			return new HashSet<ConstraintViolation<T>>();
		}
		Set<Class<?>> validateGroups = Commons.defineValidateGroups(groups);
		Set<ConstraintViolation<T>> result = new HashSet<ConstraintViolation<T>>();
		ConnectionNode propertyNode = new ConnectionNodeImpl().setElementKind(ElementKind.PROPERTY).setName(propertyName);
		Object validatedValue = value;
		PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(propertyName);
		if (propertyDescriptor.hasConstraints())
		{
			validateConstraintedElement(result, validateGroups, propertyDescriptor.getConstraintDescriptors(), propertyNode, null, validatedValue);
		}
		if (propertyDescriptor.isCascaded())
		{
			validateCascadedElement(result, validateGroups, propertyNode, validatedValue);
		}
		return result;
	}
	
	@Override
	public BeanDescriptor getConstraintsForClass(Class<?> clazz)
	{
		BeanDescriptor beanDescriptor = store.get(clazz);
		if (beanDescriptor == null)
		{
			synchronized (clazz)
			{
				beanDescriptor = store.get(clazz);
				if (beanDescriptor == null)
				{
					beanDescriptor = new BeanDescriptorImpl(clazz, (ExecutableDescriptorFactory) executableValidator, constraintValidatorFactory);
					store.put(clazz, beanDescriptor);
				}
			}
		}
		return beanDescriptor;
	}
	
	@Override
	public <T> T unwrap(Class<T> type)
	{
		throw new UnsupportedOperationException();
	}
	
	@Override
	public ExecutableValidator forExecutables()
	{
		return executableValidator;
	}
	
	@Override
	public <T> void validateBean(Set<ConstraintViolation<T>> result, Set<Class<?>> validateGroups, ConnectionNode beanNode, Object bean)
	{
		if (bean == null)
		{
			return;
		}
		BeanDescriptor beanDescriptor = getConstraintsForClass(bean.getClass());
		Set<Class<?>> mixRedefineDefaultGroup = mixRedefineDefaultGroup(validateGroups, beanDescriptor);
		Set<Class<?>> conversions = new HashSet<Class<?>>();
		for (PropertyDescriptor propertyDescriptor : beanDescriptor.getConstrainedProperties())
		{
			ConnectionNode propertyNode = beanNode.next().setElementKind(ElementKind.PROPERTY).setName(propertyDescriptor.getPropertyName());
			Object leafbean = bean;
			Object validateValue = propertyValueDescriptorFactory.getInstance(bean.getClass(), propertyDescriptor).propertyValue(leafbean);
			if (propertyDescriptor.hasConstraints())
			{
				validateConstraintedElement(result, mixRedefineDefaultGroup, propertyDescriptor.getConstraintDescriptors(), propertyNode, leafbean, validateValue);
			}
			if (propertyDescriptor.isCascaded() && validateValue != null)
			{
				conversions.clear();
				for (GroupConversionDescriptor conversionDescriptor : propertyDescriptor.getGroupConversions())
				{
					if (validateGroups.contains(conversionDescriptor.getFrom()))
					{
						conversions.add(conversionDescriptor.getTo());
					}
				}
				conversions.addAll(validateGroups);
				validateCascadedElement(result, conversions, propertyNode, validateValue);
			}
		}
		validateConstraintedElement(result, mixRedefineDefaultGroup, beanDescriptor.getConstraintDescriptors(), beanNode, null, bean);
	}
	
	private Set<Class<?>> mixRedefineDefaultGroup(Set<Class<?>> validateGroups, BeanDescriptor beanDescriptor)
	{
		Set<Class<?>> mixRedefineDefaultGroup = null;
		if (((RedefineDefaultGroupDescriptor) beanDescriptor).isReDefineDefaultGroup() && validateGroups.contains(Default.class))
		{
			mixRedefineDefaultGroup = new HashSet<Class<?>>();
			mixRedefineDefaultGroup.addAll(validateGroups);
			mixRedefineDefaultGroup.addAll(((RedefineDefaultGroupDescriptor) beanDescriptor).getRedefineDefaultGroup());
		}
		else
		{
			mixRedefineDefaultGroup = validateGroups;
		}
		return mixRedefineDefaultGroup;
	}
	
	@Override
	public <T> void validateConstraintedElement(Set<ConstraintViolation<T>> result, Set<Class<?>> validateGroups, Set<ConstraintDescriptor<?>> constraintDescriptors, ConnectionNode node, Object leafbean, Object element)
	{
		for (ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors)
		{
			boolean intersection = Commons.intersection(constraintDescriptor.getGroups(), validateGroups);
			if (intersection == false)
			{
				continue;
			}
			Boolean reportAsSingleConstrainted = validateComposingConstraint(result, node, leafbean, element, constraintDescriptor);
			if (reportAsSingleConstrainted)
			{
				continue;
			}
			validateConstraint(result, node, leafbean, element, constraintDescriptor);
		}
	}
	
	private <T> Boolean validateComposingConstraint(Set<ConstraintViolation<T>> result, ConnectionNode elementNode, Object leafbean, Object element, ConstraintDescriptor<?> constraintDescriptor)
	{
		for (ConstraintDescriptor<?> composingConstraintDescriptor : constraintDescriptor.getComposingConstraints())
		{
			boolean isValid = true;
			ConstraintValidatorContext constraintValidatorContext = new ConstraintValidatorContextImpl(elementNode, composingConstraintDescriptor, messageInterpolator, leafbean, element);
			for (ConstraintValidator<Annotation, Object> constraintValidator : ((ConstraintDescriptorImpl<?>) composingConstraintDescriptor).getConstraintValidators())
			{
				if (constraintValidator.isValid(element, constraintValidatorContext) == false)
				{
					isValid = false;
					break;
				}
			}
			if (isValid == false)
			{
				if (constraintDescriptor.isReportAsSingleViolation())
				{
					Set<ConstraintViolation<T>> generate = ((ConstraintViolationGenerator) new ConstraintValidatorContextImpl(elementNode, constraintDescriptor, messageInterpolator, leafbean, element)).generate();
					result.addAll(generate);
					return true;
				}
				else
				{
					Set<ConstraintViolation<T>> generate = ((ConstraintViolationGenerator) constraintValidatorContext).generate();
					result.addAll(generate);
				}
			}
		}
		return false;
	}
	
	private <T> void validateConstraint(Set<ConstraintViolation<T>> result, ConnectionNode elementNode, Object leafbean, Object element, ConstraintDescriptor<?> constraintDescriptor)
	{
		boolean isValid = true;
		ConstraintValidatorContext constraintValidatorContext = new ConstraintValidatorContextImpl(elementNode, constraintDescriptor, messageInterpolator, leafbean, element);
		for (ConstraintValidator<Annotation, Object> constraintValidator : ((ConstraintDescriptorImpl<?>) constraintDescriptor).getConstraintValidators())
		{
			if (constraintValidator.isValid(element, constraintValidatorContext) == false)
			{
				isValid = false;
				break;
			}
		}
		if (isValid == false)
		{
			Set<ConstraintViolation<T>> generate = ((ConstraintViolationGenerator) constraintValidatorContext).generate();
			result.addAll(generate);
		}
	}
	
	@Override
	public <T> void validateCascadedElement(Set<ConstraintViolation<T>> result, Set<Class<?>> validateGroups, ConnectionNode node, Object validateValue)
	{
		if (validateValue.getClass().isArray())
		{
			int length = Array.getLength(validateValue);
			for (int i = 0; i < length; i++)
			{
				ConnectionNode indexBeanNode = node.next().setElementKind(ElementKind.BEAN).setInIterable(true).setIndex(i);
				Object bean = Array.get(validateValue, i);
				validateBean(result, validateGroups, indexBeanNode, bean);
			}
		}
		else if (validateValue instanceof Collection<?>)
		{
			Iterator<?> iterator = ((Collection<?>) validateValue).iterator();
			int index = 0;
			while (iterator.hasNext())
			{
				Object next = iterator.next();
				ConnectionNode indexBeanNode = node.next().setElementKind(ElementKind.BEAN).setInIterable(true).setIndex(index);
				validateBean(result, validateGroups, indexBeanNode, next);
				index += 1;
			}
		}
		else if (validateValue instanceof Map<?, ?>)
		{
			for (Entry<?, ?> entry : ((Map<?, ?>) validateValue).entrySet())
			{
				Object value = entry.getValue();
				ConnectionNode valueNode = node.next().setElementKind(ElementKind.BEAN).setInIterable(true).setKey(entry.getKey());
				validateBean(result, validateGroups, valueNode, value);
			}
		}
		else
		{
			validateBean(result, validateGroups, node, validateValue);
		}
	}
	
}
